﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using Reactor.Entities;
using Reactor.Environment;
using Reactor.Resources;
using Reactor.ServiceGrid;

namespace Reactor.Providers
{
    public interface ICoreDataProvider
    {
        /// <summary>
        /// Updates the core's 'LastSeen' timestamp. If no record exists for the 
        /// specified Core, one will be created and the timestamp recorded.
        /// </summary>
        /// <param name="serviceIdentifier">The service identifier.</param>
        void UpdateCoreLastSeenTimestamp(ServiceIdentifier serviceIdentifier);

        /// <summary>
        /// Associates the service instance with the specified core. If there is already an association recorded, 
        /// the record identifier for the previous association is returned. Otherwise, one is created and returned.
        /// </summary>
        /// <param name="coreIdentifier">The core identifier.</param>
        /// <param name="serviceIdentifier">The service identifier.</param>
        /// <returns>The record identifier of the service instance.</returns>
        Guid AssociateServiceInstanceWithCore(ServiceIdentifier coreIdentifier, ServiceIdentifier serviceIdentifier);

        /// <summary>
        /// Gets a data representation of all Reactor Cores registered with the platform.
        /// </summary>
        /// <returns></returns>
        IEnumerable<ReactorCoreEntity> GetAllCores();

        /// <summary>
        /// Gets all service instances assigned to core.
        /// </summary>
        /// <param name="coreIdentifier">The core identifier.</param>
        /// <returns></returns>
        IEnumerable<ReactorServiceEntity> GetAllServiceInstancesAssignedToCore(ServiceIdentifier coreIdentifier);

        /// <summary>
        /// Disassociates the specified service instance with the Reactor Core it is assigned to.
        /// </summary>
        void DisassociateServiceInstanceWithCore(Guid serviceInstanceIdentifier);

        /// <summary>
        /// Updates the specified service's last seen timestamp.
        /// </summary>
        /// <param name="serviceIdentifier">The service identifier.</param>
        void UpdateServiceLastSeenTimestamp(ServiceIdentifier serviceIdentifier);
    }

    public class SqlCoreDataProvider : ICoreDataProvider
    {
        private readonly string _connectionString;

        /// <summary>
        /// Initializes a new instance of the <see cref="SqlCoreDataProvider"/> class.
        /// </summary>
        public SqlCoreDataProvider()
        {
            var platformConnectionData = Context.ServiceLocator.GetInstance<PlatformConnectionData>();
            _connectionString = platformConnectionData.DatabaseConnectionString;

            if(string.IsNullOrEmpty(_connectionString))
                throw new InvalidOperationException("No connection string found.");
        }

        #region Implementation of ICoreDataProvider

        /// <summary>
        /// Updates the core's 'LastSeen' timestamp. If no record exists for the 
        /// specified Core, one will be created and the timestamp recorded.
        /// </summary>
        /// <param name="serviceIdentifier">The service identifier.</param>
        public void UpdateCoreLastSeenTimestamp(ServiceIdentifier serviceIdentifier)
        {
            if (serviceIdentifier == null) throw new ArgumentNullException("serviceIdentifier");
            if (string.IsNullOrEmpty(serviceIdentifier.Name)) throw new ArgumentException("Name cannot be null or empty.");
            if (serviceIdentifier.Version == null) throw new ArgumentException("Version cannot be null.");

            const string proc = "rsp_UpdateCoreLastSeenTimeStamp";
            using (var sqlConnection = new SqlConnection(_connectionString))
            {
                using (var cmd = new SqlCommand(proc))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    sqlConnection.Open();
                    cmd.Connection = sqlConnection;

                    cmd.Parameters.AddWithValue("@CoreName", serviceIdentifier.Name);
                    cmd.Parameters.AddWithValue("@CoreVersion", serviceIdentifier.Version.ToString());
                    cmd.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// Updates the specified service's last seen timestamp.
        /// </summary>
        /// <param name="serviceIdentifier">The service identifier.</param>
        public void UpdateServiceLastSeenTimestamp(ServiceIdentifier serviceIdentifier)
        {
            
        }

        /// <summary>
        /// Associates the service instance with the specified core. If there is already an association recorded, 
        /// the record identifier for the previous association is returned. Otherwise, one is created and returned.
        /// </summary>
        /// <param name="coreIdentifier">The core identifier.</param>
        /// <param name="serviceIdentifier">The service identifier.</param>
        /// <returns>The record identifier of the service instance.</returns>
        public Guid AssociateServiceInstanceWithCore(ServiceIdentifier coreIdentifier, ServiceIdentifier serviceIdentifier)
        {
            Guid instanceIdentifier = Guid.Empty;

            using (var sqlConnection = new SqlConnection(_connectionString))
            {
                using (var cmd = new SqlCommand("dbo.rsp_AssociateServiceInstanceWithCore"))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@CoreName", coreIdentifier.Name);
                    cmd.Parameters.AddWithValue("@CoreVersion", coreIdentifier.Version.ToString());
                    cmd.Parameters.AddWithValue("@ServiceName", serviceIdentifier.Name);
                    cmd.Parameters.AddWithValue("@ServiceVersion", serviceIdentifier.Version.ToString());
                    sqlConnection.Open();
                    cmd.Connection = sqlConnection;

                    using (var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                    {
                        while (rdr.Read())
                        {
                            instanceIdentifier = rdr.GetGuid(rdr.GetOrdinal("InstanceIdentifier"));
                        }
                    }
                }
            }

            return instanceIdentifier;
        }

        /// <summary>
        /// Disassociates the specified service instance with the Reactor Core it is assigned to.
        /// </summary>
        public void DisassociateServiceInstanceWithCore(Guid serviceInstanceIdentifier)
        {
            if (serviceInstanceIdentifier == Guid.Empty) throw new ArgumentException(CommonResources.Error_GuidCannotBeEmpty, "serviceInstanceIdentifier");

            using (var sqlConnection = new SqlConnection(_connectionString))
            {
                using (var cmd = new SqlCommand("dbo.rsp_DisassociateServiceInstanceWithCore"))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@ServiceInstanceIdentifier", serviceInstanceIdentifier);
                    sqlConnection.Open();
                    cmd.Connection = sqlConnection;
                    cmd.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// Gets a data representation of all Reactor Cores registered with the platform.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ReactorCoreEntity> GetAllCores()
        {
            var results = new List<ReactorCoreEntity>();

            using (var sqlConnection = new SqlConnection(_connectionString))
            {
                using (var cmd = new SqlCommand("dbo.rsp_GetAllCores"))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    sqlConnection.Open();
                    cmd.Connection = sqlConnection;

                    using (var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                    {
                        if (rdr != null)
                        {
                            while (rdr.Read())
                            {
                                var core = new ReactorCoreEntity(this)
                                               {
                                                   Identifier = new ServiceIdentifier
                                                                    {
                                                                        Name = rdr.GetString(rdr.GetOrdinal("Name")),
                                                                        Version = new Version(rdr.GetString(rdr.GetOrdinal("Version")))
                                                                    },
                                                   LastSeen = rdr.GetDateTime(rdr.GetOrdinal("LastSeen"))
                                               };
                                results.Add(core);
                            }
                        }
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// Gets all service instances assigned to core.
        /// </summary>
        /// <param name="coreIdentifier">The core identifier.</param>
        /// <returns></returns>
        public IEnumerable<ReactorServiceEntity> GetAllServiceInstancesAssignedToCore(ServiceIdentifier coreIdentifier)
        {
            var results = new List<ReactorServiceEntity>();
            using (var sqlConnection = new SqlConnection(_connectionString))
            {
                using (var cmd = new SqlCommand("dbo.rsp_GetAllServiceInstancesAssignedToCore"))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@CoreName", coreIdentifier.Name);

                    sqlConnection.Open();
                    cmd.Connection = sqlConnection;

                    using (var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                    {
                        if (rdr != null)
                        {
                            while (rdr.Read())
                            {
                                var service = new ReactorServiceEntity
                                                  {
                                                      InstanceIdentifier = rdr.GetGuid(rdr.GetOrdinal("ServiceId")),
                                                      Identifier = new ServiceIdentifier
                                                                       {
                                                                           Name = rdr.GetString(rdr.GetOrdinal("Name")),
                                                                           Version = new Version(rdr.GetString(rdr.GetOrdinal("Version")))
                                                                       }
                                                  };
                                results.Add(service);
                            }
                        }
                    }
                }
            }
            return results;
        }

        #endregion
    }
}
